﻿using System;
using System.Text;
using GreyMagic;
using Hyperion.Native;

namespace Hyperion
{
    /// <summary>
    /// A piece of allocated memory within a process.
    /// </summary>
    public class AllocatedMemory : IDisposable
    {
        private int _currentOffset;
        public IntPtr Address { get; private set; }
        public int Size { get; private set; }
        public Memory Memory { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="AllocatedMemory"/> class.
        /// </summary>
        /// <param name="memory">The memory.</param>
        /// <param name="size">The size.</param>
        public AllocatedMemory(Memory memory, int size)
        {
            Memory = memory;
            Size = size;

            Address = Allocate();
        }

        internal IntPtr Allocate()
        {
            if (Memory == null || Size == 0)
                return IntPtr.Zero; // Nothing to allocate!

            return Win32.VirtualAllocEx(Memory.ProcessHandle, 0x0, Size, MemoryAllocationType.MEM_COMMIT,
                                        MemoryProtectionType.PAGE_EXECUTE_READWRITE);
        }

        #region Reading and Writing members

        /// <summary>
        /// Writes the specified value to this codecave.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <param name="offset">The offset.</param>
        /// <param name="isRelative">if set to <c>true</c> [is relative].</param>
        public int Write<T>(T value, int offset = 0, bool isRelative = false) where T : struct
        {
            return Memory.Write(Address + offset, value, isRelative);
        }

        /// <summary>
        /// Writes the string.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="encoding">The encoding.</param>
        /// <param name="offset">The offset.</param>
        /// <param name="isRelative">if set to <c>true</c> [is relative].</param>
        public bool WriteString(string value, Encoding encoding, int offset = 0, bool isRelative = false)
        {
            return Memory.WriteString(Address + offset, value, encoding, isRelative);
        }

        /// <summary>
        /// Writes the specified bytes to memory.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public int WriteBytes(byte[] value)
        {
            return Memory.WriteBytes(Address, value);
        }

        /// <summary>
        /// Reads the next value from this code cave, moving the index pointer to the next type after reading.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T ReadNext<T>() where T : struct
        {
            var ret = Memory.Read<T>(Address + _currentOffset);

            _currentOffset += MarshalCache<T>.Size;

            return ret;
        }

        /// <summary>
        /// Reads the value of the specified type at the specified offset relative to the address of the allocated memory.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="offset">The offset.</param>
        /// <returns></returns>
        public T Read<T>(int offset) where T : struct
        {
            return Memory.Read<T>(Address + offset);
        }

        /// <summary>
        /// Reads a zero-terminated string from the specified offset in the allocated memory.
        /// </summary>
        /// <returns></returns>
        public string ReadString(int offset = 0)
        {
            return Memory.ReadString(Address, Encoding.ASCII);
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            // Call VirtualFreeEx as long as we've actually gotten an address from VirtualAllocEx.
            if (Address != IntPtr.Zero)
                Win32.VirtualFreeEx(Memory.ProcessHandle, Address, Size, MemoryFreeType.MEM_RELEASE);
        }

        #endregion
    }
}
